﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using DynORM.Interfaces;
using DynORM.ValueObjects;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;

namespace DynORM.ReferenceObjects
{

    [XmlRoot(ElementName = RowDescriptor.RowDescriptorTag)]
    public class RowDescriptor : IRowDescriptor, IXmlSerializable
    {
        #region fields
        private List<IFieldDescriptor> fieldDescriptors = new List<IFieldDescriptor>();
        private List<IRelationDescriptor> relationDescriptors = new List<IRelationDescriptor>();
        private string tableName;  //had to relax "readonly" restriction of this member in favor to init logic of xml serializer
        #endregion

        //uniquely identify RowDescriptor. Does not change during row descriptor's lifetime
        public string TableName
        {
            get
            {
                return this.tableName;
            }
        }

        public IPrimaryKeyDescriptor PrimaryKeyDescriptor
        {
            get
            {
                return (IPrimaryKeyDescriptor)this.relationDescriptors.FirstOrDefault<IRelationDescriptor>(r => r.RelationType == RelationType.PrimaryKey);
            }
        }

        public RowDescriptor(string tableName, IEnumerable<IFieldDescriptor> fieldDescriptors, IEnumerable<IRelationDescriptor> relationDescriptors)
            :this(tableName)
        {
            foreach (IRelationDescriptor relationDesc in relationDescriptors)
                AddRelation(relationDesc);

            foreach (IFieldDescriptor fieldDesc in fieldDescriptors)
                AddField(fieldDesc);
        }

        public RowDescriptor(string tableName)
        {
            this.tableName = tableName;
        }

        //for serializer
        private RowDescriptor()
        { }

        public const string RowDescriptorTag = "RowDescriptor";
        public const string RowDescriptorTableNameAttr = "TableName";
        public const string RowDescriptorForeignKeysTag = "ForeignKeyDescriptors";
        public const string RowDescriptorFieldsTag = "FieldDescriptors";

        //relations
        public IEnumerable<IRelationDescriptor> RelationDescriptors
        {
            get { return this.relationDescriptors; }
        }

        public IEnumerable<IForeignKeyDescriptor> ForeignKeyDescriptors 
        {
            get 
            {
                return this.relationDescriptors.Where<IRelationDescriptor>(rd => rd.RelationType == RelationType.ForeignKey).Cast<IForeignKeyDescriptor>();
            } 
        }

        public void AddRelation(IRelationDescriptor relationDescriptor)
        {
            this.relationDescriptors.Add(relationDescriptor);
        }

        public void RemoveRelation(IRelationDescriptor relationDescriptor)
        {
            this.relationDescriptors.Remove(relationDescriptor);
        }

        public IEnumerable<IFieldDescriptor> FieldDescriptors
        {
            get { return this.fieldDescriptors; }
        }

        public void AddField(IFieldDescriptor fieldDescriptor)
        {
            this.fieldDescriptors.Add(fieldDescriptor);
        }

        public void RemoveField(IFieldDescriptor fieldDescriptor)
        {
            this.fieldDescriptors.Remove(fieldDescriptor);
        }

        public IFieldDescriptor GetField(string fieldName)
        { 
            IFieldDescriptor foundFieldDescriptor = null;
            foreach (IRelationDescriptor rd in this.RelationDescriptors)
            {
                foundFieldDescriptor = rd.FieldDescriptors.FirstOrDefault<KeyValuePair<int, IFieldDescriptor>>(kv => String.Compare(kv.Value.FieldName, fieldName, StringComparison.OrdinalIgnoreCase) == 0).Value;
                if (foundFieldDescriptor != null)
                    return foundFieldDescriptor;
            }

            foundFieldDescriptor = this.fieldDescriptors.Find(match => String.Compare(match.FieldName, fieldName, StringComparison.OrdinalIgnoreCase) == 0);
            if (foundFieldDescriptor != null)
                return foundFieldDescriptor;

            return null;
        }

        public bool ContainsField(string fieldName)
        {
            if (GetField(fieldName) != null)
                return true;
            else
                return false;
        }

        public CheckIntegrityResult CheckStructureIntegrity(bool usePredefinedConvention)
        {
            CheckIntegrityResult passed = new CheckIntegrityResult(true, String.Empty);

            foreach (IRelationDescriptor rd in this.relationDescriptors)
                if (rd.CheckStructureIntegrity(false) != passed)
                    return new CheckIntegrityResult(false, "A RelationDescriptor contained in the RowDescriptor failed to pass its integrity check. See inner CheckIntegrityResult.", rd.CheckStructureIntegrity(false));

            //if (this.relationDescriptors.Count<IRelationDescriptor>(r => r.RelationType == RelationType.PrimaryKey) != 1)
            //    return new CheckIntegrityResult(false, "RelationDescriptors collection should contain at least one and only one PrimaryKey.");
            if (this.relationDescriptors.Count<IRelationDescriptor>(r => r.RelationType == RelationType.PrimaryKey) > 1)
                return new CheckIntegrityResult(false, "RelationDescriptors collection should contain no more than one PrimaryKey.");

            foreach (IRelationDescriptor rd in this.relationDescriptors)
            {
                if (String.Compare(this.TableName, rd.TableName, StringComparison.OrdinalIgnoreCase) != 0)
                    return new CheckIntegrityResult(false, "Given relations have values of TableName property which are not corresponding to each other.");
            }

            foreach (IFieldDescriptor fd in this.fieldDescriptors)
                if (fd.CheckStructureIntegrity(false) != passed)
                    return new CheckIntegrityResult(false, "A FieldDescriptor contained in the RowDescriptor failed to pass its integrity check. See inner CheckIntegrityResult.", fd.CheckStructureIntegrity(false));

            return passed;
        }

        private class EqualityComparer : IEqualityComparer<string>
        {
            public bool  Equals(string x, string y)
            {
 	            if (String.Compare(x, y, StringComparison.OrdinalIgnoreCase) == 0)
                    return true;
                else 
                    return false;
            }

            public int  GetHashCode(string obj)
            {
 	            return obj.GetHashCode();
            }
        }

        private string GenerateUniqueName(string item, IEnumerable<string> list)
        {
            if (!list.Contains<string>(item, new EqualityComparer())) return null;

            int i = 0;
            string result = null;
            while (list.Contains<string>(result ?? item, new EqualityComparer()))
            {
                result = item + String.Format("_{0}", ++i);
            }
            return result;
        }

        public void InforceRelationNameUniqness()
        {
            for (int i = 0; i < this.relationDescriptors.Count; i++)
            {
                List<IRelationDescriptor> relationDescriptorsCopy = new List<IRelationDescriptor>();
                relationDescriptorsCopy.AddRange(this.relationDescriptors);
                relationDescriptorsCopy.RemoveAt(i);
                IEnumerable<string> otherRelationNames = relationDescriptorsCopy.Select<IRelationDescriptor, string>(rd => rd.Name); 
                string newRelName = GenerateUniqueName(this.relationDescriptors[i].Name, otherRelationNames);
                if (newRelName != null)
                    this.relationDescriptors[i] = AuxRelation.ChangeRelationDescName(this.relationDescriptors[i], newRelName);
            }
        }

        public void InforceFieldNameUniqness()
        {
            List<KeyValuePair<IFieldDescriptor, IRelationDescriptor>> flattenFieldDescsList =
                new List<KeyValuePair<IFieldDescriptor, IRelationDescriptor>>();
            foreach (IFieldDescriptor fd in this.FieldDescriptors)
                flattenFieldDescsList.Add(new KeyValuePair<IFieldDescriptor, IRelationDescriptor>(fd, null));
            foreach (IRelationDescriptor rd in this.RelationDescriptors)
                foreach (IFieldDescriptor fd in rd.FieldDescriptors.Select<KeyValuePair<int, IFieldDescriptor>, IFieldDescriptor>(kv => kv.Value))
                    flattenFieldDescsList.Add(new KeyValuePair<IFieldDescriptor, IRelationDescriptor>(fd, rd));

            for (int i = 0; i < flattenFieldDescsList.Count; i++)
            {
                List<KeyValuePair<IFieldDescriptor, IRelationDescriptor>> flattenFieldDescsListCopy =
                    new List<KeyValuePair<IFieldDescriptor, IRelationDescriptor>>();
                flattenFieldDescsListCopy.AddRange(flattenFieldDescsList);
                flattenFieldDescsListCopy.RemoveAt(i);
                IEnumerable<string> otherFieldNames = flattenFieldDescsListCopy.Select<KeyValuePair<IFieldDescriptor, IRelationDescriptor>, string>(p => p.Key.FieldName);
                string newFieldName = GenerateUniqueName(flattenFieldDescsList[i].Key.FieldName, otherFieldNames);

                if (newFieldName != null)
                {
                    IFieldDescriptor newFieldDescriptor = new FieldDescriptor(flattenFieldDescsList[i].Key.FieldType, newFieldName);
                    if (flattenFieldDescsList[i].Value != null)//it is IRelationDesc's IFieldDesc
                    {
                        this.RemoveRelation(flattenFieldDescsList[i].Value);
                        IRelationDescriptor newRD;
                        if (flattenFieldDescsList[i].Value is IPrimaryKeyDescriptor)
                            newRD = AuxRelation.ChangeFieldDescInRelationDesc((IPrimaryKeyDescriptor)flattenFieldDescsList[i].Value, flattenFieldDescsList[i].Key, newFieldDescriptor);
                        else
                            newRD = AuxRelation.ChangeFieldDescInRelationDesc((IForeignKeyDescriptor)flattenFieldDescsList[i].Value, flattenFieldDescsList[i].Key, newFieldDescriptor);

                        this.AddRelation(newRD);

                        flattenFieldDescsList[i] = new KeyValuePair<IFieldDescriptor, IRelationDescriptor>(newFieldDescriptor, flattenFieldDescsList[i].Value);
                    }
                    else //this IFieldDesc is not in IRelationDesc
                    {
                        this.RemoveField(flattenFieldDescsList[i].Key);
                        this.AddField(newFieldDescriptor);

                        flattenFieldDescsList[i] = new KeyValuePair<IFieldDescriptor, IRelationDescriptor>(newFieldDescriptor, null);
                    }
                }
            }
        }


        #region IXmlSerializable and auxiliary CreateFromXml member
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, RowDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        if (string.Compare(reader.Name, RowDescriptorTableNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            this.tableName = reader.Value;
                                    }
                                }
                                if (emptyElement)
                                    return;
                            }
                            else if (string.Compare(reader.Name, DynORM.ValueObjects.PrimaryKeyDescriptor.PrimaryKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                PrimaryKeyDescriptor pkd = DynORM.ValueObjects.PrimaryKeyDescriptor.CreateFromXml(reader);
                                this.relationDescriptors.Add(pkd);
                            }
                            else if (string.Compare(reader.Name, RowDescriptorForeignKeysTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, ForeignKeyDescriptor.ForeignKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                ForeignKeyDescriptor fkd = ForeignKeyDescriptor.CreateFromXml(reader);
                                this.relationDescriptors.Add(fkd);
                            }
                            else if (string.Compare(reader.Name, RowDescriptorFieldsTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, FieldDescriptor.FieldDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                FieldDescriptor fd = FieldDescriptor.CreateFromXml(reader);
                                this.fieldDescriptors.Add(fd);
                            }
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, RowDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return;
                        } break;
                    default: continue;
                }
            } while (reader.Read());
        }

        public static RowDescriptor CreateFromXml(XmlReader reader)
        {
            string tableName = String.Empty;
            List<IRelationDescriptor> relationDescriptors = new List<IRelationDescriptor>();
            List<IFieldDescriptor> fieldDescriptors = new List<IFieldDescriptor>();

            do
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        {
                            if (string.Compare(reader.Name, RowDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                bool emptyElement = reader.IsEmptyElement;
                                if (reader.HasAttributes)
                                {
                                    while (reader.MoveToNextAttribute())
                                    {
                                        if (string.Compare(reader.Name, RowDescriptorTableNameAttr, StringComparison.OrdinalIgnoreCase) == 0)
                                            tableName = reader.Value;
                                    }
                                }
                                if (emptyElement)
                                    return new RowDescriptor(tableName, fieldDescriptors, relationDescriptors);
                            }
                            else if (string.Compare(reader.Name, DynORM.ValueObjects.PrimaryKeyDescriptor.PrimaryKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                PrimaryKeyDescriptor pkd = DynORM.ValueObjects.PrimaryKeyDescriptor.CreateFromXml(reader);
                                relationDescriptors.Add(pkd);
                            }
                            else if (string.Compare(reader.Name, RowDescriptorForeignKeysTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, ForeignKeyDescriptor.ForeignKeyDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                ForeignKeyDescriptor fkd = ForeignKeyDescriptor.CreateFromXml(reader);
                                relationDescriptors.Add(fkd);
                            }
                            else if (string.Compare(reader.Name, RowDescriptorFieldsTag, StringComparison.OrdinalIgnoreCase) == 0)
                            { }
                            else if (string.Compare(reader.Name, FieldDescriptor.FieldDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                FieldDescriptor fd = FieldDescriptor.CreateFromXml(reader);
                                fieldDescriptors.Add(fd);
                            }
                        } break;
                    case XmlNodeType.EndElement:
                        {
                            if (string.Compare(reader.Name, RowDescriptorTag, StringComparison.OrdinalIgnoreCase) == 0)
                                return new RowDescriptor(tableName, fieldDescriptors, relationDescriptors);
                        } break;
                    default: continue;
                }
            } while (reader.Read());
            return new RowDescriptor(tableName, fieldDescriptors, relationDescriptors);
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteAttributeString(RowDescriptorTableNameAttr, this.TableName);

            writer.WriteStartElement(DynORM.ValueObjects.PrimaryKeyDescriptor.PrimaryKeyDescriptorTag);
            ((PrimaryKeyDescriptor)this.relationDescriptors.First<IRelationDescriptor>(rd => rd.RelationType == RelationType.PrimaryKey)).WriteXml(writer);
            writer.WriteEndElement();

            if (this.RelationDescriptors.Any<IRelationDescriptor>(rd => rd.RelationType == RelationType.ForeignKey))
            {
                writer.WriteStartElement(RowDescriptorForeignKeysTag);
                foreach (ForeignKeyDescriptor fkd in this.RelationDescriptors.Where<IRelationDescriptor>(rd => rd.RelationType == RelationType.ForeignKey))
                {
                    writer.WriteStartElement(ForeignKeyDescriptor.ForeignKeyDescriptorTag);
                    fkd.WriteXml(writer);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }

            if (this.FieldDescriptors.Count() > 0)
            {
                writer.WriteStartElement(RowDescriptorFieldsTag);
                foreach (FieldDescriptor fd in this.FieldDescriptors)
                {
                    writer.WriteStartElement(FieldDescriptor.FieldDescriptorTag);
                    fd.WriteXml(writer);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();
            }
        }
        #endregion    

    }

}
